package com.stottlerhenke.simbionic.editor.gui;

import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Point;
import java.awt.Window;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyEvent;
import java.lang.reflect.Modifier;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.Stack;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingConstants;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.PlainDocument;
import javax.swing.tree.DefaultMutableTreeNode;

import com.stottlerhenke.simbionic.editor.SB_Action;
import com.stottlerhenke.simbionic.editor.SB_Behavior;
import com.stottlerhenke.simbionic.editor.SB_Class;
import com.stottlerhenke.simbionic.editor.SB_ClassMember;
import com.stottlerhenke.simbionic.editor.SB_ClassMethod;
import com.stottlerhenke.simbionic.editor.SB_ClassMethodParameter;
import com.stottlerhenke.simbionic.editor.SB_Constant;
import com.stottlerhenke.simbionic.editor.SB_Function;
import com.stottlerhenke.simbionic.editor.SB_Global;
import com.stottlerhenke.simbionic.editor.SB_Parameter;
import com.stottlerhenke.simbionic.editor.SB_Predicate;
import com.stottlerhenke.simbionic.editor.SB_Variable;
import com.stottlerhenke.simbionic.editor.SimBionicEditor;
import com.stottlerhenke.simbionic.editor.gui.api.EditorRegistry;
import com.stottlerhenke.simbionic.editor.gui.api.I_EditorListener;
import com.stottlerhenke.simbionic.editor.gui.api.I_ExpressionEditor;

public class SB_Autocomplete extends JTextField implements Comparator
{

    protected SimBionicEditor _editor;

    protected SB_GlassPane _glassPane = new SB_GlassPane();

    protected boolean _returnsValue = true;

    protected Vector _actionsBehaviors = new Vector();

    protected Vector _predicates = new Vector();

    protected Vector _variables = new Vector();

    protected Vector _matchList = new Vector();

    protected int _matchSel = -1;

    protected boolean _needToComplete = false;

    protected boolean _escapePressed = false;

    protected boolean _ignoreCaretUpdate = false;
    
    protected boolean _ignoreFocusGained = false;
    
    public SB_Autocomplete(int cols, SimBionicEditor editor)
    {
        super(cols);
        setFocusTraversalKeysEnabled(false);

        _editor = editor;

        addFocusListener(new FocusListener()
        {

            public void focusGained(FocusEvent event)
            {
            	// TODO  custom editors set _ignoreFocusGained so that
            	// we don't reinvoke the editor when it returns the focus
            	// after editing
            	if (!_ignoreFocusGained) { 
            		if (!tryCustomEdit()) {
            			initializeNames();
            			_glassPane.setLocation(SB_Autocomplete.this);
            			_glassPane.setVisible(true);
            			_escapePressed = false;
            		}
            	} else {
            		_ignoreFocusGained = false;
            		fireActionPerformed();
            	}
            }

            public void focusLost(FocusEvent event)
            {
                _glassPane.setVisible(false);
                clearNames();
            }
        });

        addCaretListener(new CaretListener()
        {

            public void caretUpdate(CaretEvent event)
            {
                // used to make canvas more responsive
                if (_ignoreCaretUpdate)
                    return;

                if (ComponentRegistry.getFrame() != null
                        && ComponentRegistry.getFrame().getFocusOwner() != SB_Autocomplete.this)
                {
                    requestFocus();
                    initializeNames();
                }
                performAutocomplete(getText(), event.getDot());
            }
        });
    }

    public void processKeyEvent(KeyEvent event)
    {
        int id = event.getID();
        switch (event.getKeyCode())
        {
        case KeyEvent.VK_UP:
            if (id == KeyEvent.KEY_PRESSED)
            {
                int size = _matchList.size();
                if (size > 1)
                {
                    --_matchSel;
                    if (_matchSel == -1)
                        _matchSel = size - 1;
                    _glassPane.setText(generateMatchText(_matchList, _matchSel));
                }
            }
            return;
        case KeyEvent.VK_DOWN:
            if (id == KeyEvent.KEY_PRESSED)
            {
                int size = _matchList.size();
                if (size > 1)
                {
                    ++_matchSel;
                    if (_matchSel == size)
                        _matchSel = 0;
                    _glassPane.setText(generateMatchText(_matchList, _matchSel));
                }
            }
            return;
        case KeyEvent.VK_TAB:
            if (id == KeyEvent.KEY_PRESSED && _needToComplete)
            {
                String str = (String) _matchList.get(_matchSel);
                if (str.charAt(0) != '"')
                {
                    int pos = str.indexOf('(');
                    if (pos != -1)
                        str = str.substring(0, pos);
                    else
                    {
                        pos = str.indexOf(':');
                        if (pos != -1)
                            str = str.substring(0, pos - 1);
                    }
                }
                
                str = getInsertString(str);
                String text = getText();
                int caretPos = getCaretPosition();
                text = text.substring(0, caretPos) + str + text.substring(caretPos);
                setText(text);
                caretPos += str.length();
                setCaretPosition(caretPos);
                
                // if we just finished a completion for a function that has a custom editor,
                // launch that custom editor
                final SB_ParseInfo info = new SB_ParseInfo();
                final int end = caretPos;
                parseFunction(getText(), caretPos, info);
                String expr = text.substring(info.first, end);
                I_ExpressionEditor editor = _editor.getEditorRegistry().getExpressionEditor(
                		EditorRegistry.EXPRESSION_TYPE_FUNCTION, 
                		info.funcName, 
                		expr);
                if (editor != null) {
                	editor.editObject(expr, new I_EditorListener() {
                		public void editingCanceled(I_ExpressionEditor source) {}

                		public void editingCompleted(I_ExpressionEditor source, String result) {
                			String text = getText();
                			setText(text.substring(0, info.first) + result + text.substring(end));
                			setCaretPosition(info.first + result.length());
                			_ignoreFocusGained = true;
                		}
                	});
                }
            }
            return;
        case KeyEvent.VK_ESCAPE:
            if (id == KeyEvent.KEY_PRESSED)
            {
                _escapePressed = true;
                ComponentRegistry.getFrame().requestFocus();
            }
            return;
        default:
        }
        super.processKeyEvent(event);
    }
    
    protected Document createDefaultModel()
    {
        return new PlainDocument()
        {

            public void insertString(int offs, String str, AttributeSet a)
                    throws BadLocationException
            {
                if (str == null)
                    return;
                if (_needToComplete && str.length() == 1)
                {
                    char c = str.charAt(0);
                    String line = (String) _matchList.get(_matchSel);
                    int pos = line.indexOf('(');
                    if (c == '(')
                    {
                        if (pos != -1)
                            str = line.substring(0, pos + 1);
                    } else if (c == ',' || c == ')' || c == ' ' || c == '*' || c == '/' || c == '+'
                            || c == '-' || c == '=' || c == '<' || c == '>' || c == '!' || c == '.'
                            || c == '&' || c == '|')
                    {
                        if (line.charAt(0) == '"')
                            str = line + c;
                        else if (pos == -1)
                        {
                            pos = line.indexOf(':');
                            str = line.substring(0, pos - 1) + c;
                        }
                    } else if (c == '"')
                    {
                        if (line.charAt(0) == '"')
                            str = line;
                    }

                    if (str.length() > 1)
                        str = getInsertString(str);
                }
                super.insertString(offs, str, a);
            }
        };
    }

    protected String getInsertString(String str)
    {
        String text = getText();
        int pos = getCaretPosition() - 1;
        char c = text.charAt(pos);
        if (str.charAt(0) == '"')
        {
            String line = (String) _matchList.get(_matchSel);
            if (line.equals(text.substring(Math.max(0, pos + 1 - line.length()), pos + 1)))
                pos -= line.length();
            else
            {
                while (c != '"')
                {
                    --pos;
                    c = text.charAt(pos);
                }
                --pos;
            }
        } else
        {
            while ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')
                    || c == '_')
            {
                --pos;
                if (pos == -1)
                    break;
                c = text.charAt(pos);
            }
        }
        return str.substring(getCaretPosition() - (pos + 1));
    }

    protected boolean tryCustomEdit() {
    	// if the expression can be edited with a custom editor,
    	// launch the custom editor
    	String text = getText();
    	final SB_ParseInfo info = new SB_ParseInfo();
        parseFunction(text, text.length(), info);
        // expression must be a sequence of word characters (letters, digits, underscores)
        // followed by an open paren, and must have matching parens
        Pattern p = Pattern.compile("(\\w+)\\(.*");
        Matcher m = p.matcher(text);
        if (info.paren == 0 && m.matches()) {
        	String function = m.group(1);
        	I_ExpressionEditor editor = _editor.getEditorRegistry().getExpressionEditor(
        			EditorRegistry.EXPRESSION_TYPE_FUNCTION, 
        			function, 
        			text);
        	if (editor != null) {
        		editor.editObject(text, new I_EditorListener() {
        			public void editingCanceled(I_ExpressionEditor source) {}

        			public void editingCompleted(I_ExpressionEditor source, String result) {
        				setText(result);
        				_ignoreFocusGained = true;
        			}
        		});
        		return true;
        	}
        }
        return false;
    }
    
    protected void initializeNames()
    {
        if (_variables.size() > 0)
            return;

        _glassPane.setText(null);

        SB_ProjectBar projectBar = (SB_ProjectBar) ComponentRegistry.getProjectBar();
        SB_Catalog catalog = projectBar._catalog;
        Enumeration e = catalog.getRoot().preorderEnumeration();
        DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode) e.nextElement();
        Object userObject;
        while (e.hasMoreElements())
        {
            treeNode = (DefaultMutableTreeNode) e.nextElement();
            userObject = treeNode.getUserObject();
            // add actions and behaviors
            if (!_returnsValue
                    && ((userObject instanceof SB_Action) || (userObject instanceof SB_Behavior)))
                _actionsBehaviors.add(treeNode);
            // add predicates
            else if (userObject instanceof SB_Predicate)
                _predicates.add(treeNode);
            // add globals and constants
            else if ((userObject instanceof SB_Global) || (userObject instanceof SB_Constant))
                _variables.add(userObject);
        }
        // add locals
        SB_TabbedCanvas tabbedCanvas = (SB_TabbedCanvas) ComponentRegistry.getContent();
        SB_Polymorphism poly = tabbedCanvas.getActiveCanvas()._poly;
        DefaultMutableTreeNode locals = poly._locals;
        int size = locals.getChildCount();
        for (int i = 0; i < size; ++i)
        {
            SB_Variable local = (SB_Variable) ((DefaultMutableTreeNode) locals.getChildAt(i))
                    .getUserObject();
            _variables.add(local);
        }
        // add parameters
        DefaultMutableTreeNode params = catalog.findNode(poly._parent, catalog._behaviors);
        size = params.getChildCount();
        for (int i = 0; i < size; ++i)
        {
            SB_Parameter param = (SB_Parameter) ((DefaultMutableTreeNode) params.getChildAt(i))
                    .getUserObject();
            _variables.add(param);
        }

        if (!_returnsValue)
            Collections.sort(_actionsBehaviors, this);
        Collections.sort(_predicates, this);
        Collections.sort(_variables, this);
    }

    protected void clearNames()
    {
        _actionsBehaviors.removeAllElements();
        _predicates.removeAllElements();
        _variables.removeAllElements();
        _matchList.removeAllElements();
        _matchSel = -1;
        _needToComplete = false;
    }

    public int compare(Object object1, Object object2)
    {
        return object1.toString().compareToIgnoreCase(object2.toString());
    }

    protected class SB_ParseInfo
    {

        public String funcName;

        public String paramName;

        public int first;
        
        public int index;

        public int paren;
        
        public String toString(){
        	return "func: " + funcName + ", param: " + paramName + " @" + index + ", " + paren;
        }
    }

    protected void performAutocomplete(String expr, int pos)
    {
        _matchList.removeAllElements();
        _needToComplete = false;

        // search for latest string pattern of the form PREDICATE(...,PARAMETER
        // note PARAMETER could be a partial predicate
        // also note PARAMETER not parsed when current position not within
        // parenthesis
        SB_ParseInfo info = new SB_ParseInfo();
        parseFunction(expr, pos, info);
        if (_returnsValue)
            info.paren = -1; // ignore paren
        if (info.index != -1) // parameter parsed, i.e. current position
        // within parenthesis
        {
        	if (info.paramName != null && info.paramName.indexOf('.') > 0)
        	{
        		matchClassMemberOrMethod(info.paramName, false, info.index);
        	}
        	else
        	{
        		matchPartialFunction(_matchList, info.paramName, info.paren == 0);
        		matchPartialVariable(_matchList, info.paramName);
        		matchPartialExtra(_matchList, expr, pos, info);
        	}
        	
            if (!_matchList.isEmpty()) // parameter is partial predicate or
            // variable
            {
                // look for '(' to auto-complete
                // if (bOpenParen || bNonAlphaNum)
                // OnDblclkListPred();
                _needToComplete = true;
            } else
            // parameter is not partial predicate or variable, move to function
            {
            	if (info.funcName != null && info.funcName.indexOf('.') > 0)
            	{
            		matchClassMemberOrMethod(info.funcName, (info.paren == 1), info.index);
            	}
            	else
            	{
                    matchFunction(_matchList, info.funcName, info.index, info.paren == 1);
            	}
            	
                if (!_matchList.isEmpty()) // function found
                {
                    if (info.paren == 1)
                    {
                    }
                } else
                // function unknown
                {
                }
            }
        } else
        // parameter not parsed, i.e. current position not within parenthesis
        {
        	if (info.funcName != null && info.funcName.indexOf('.') > 0)
        	{
        		matchClassMemberOrMethod(info.funcName, (info.paren == 0), info.index);
        	}
        	else
        	{
        		matchPartialFunction(_matchList, info.funcName, info.paren == 0); // partial function
        		if (info.paren != 0)
        			matchPartialVariable(_matchList, info.funcName); // partial variable
        	}
        	
            if (!_matchList.isEmpty())
                _needToComplete = true;
        }

        if (_matchList.isEmpty())
        {
            _matchSel = -1;
            _glassPane.setText(null);
        } else
        {
            _matchSel = 0;
            _glassPane.setText(generateMatchText(_matchList, _matchSel));
        }
    }

    protected String generateMatchText(Vector matchList, int matchSel)
    {
        String text = "";
        String line;
        int pos;
        int size = matchList.size();
    	int start = -1, end = -1;
        for (int i = 0; i < size; ++i)
        {
            line = (String) matchList.get(i);
            pos = line.indexOf('@');
            if (pos >= 0)
            {
            	start = Integer.parseInt(line.substring(pos+1, line.indexOf(',', pos)));
            	end = Integer.parseInt(line.substring(line.indexOf(',', pos)+1));
            	line = line.substring(0, pos);
            }
            
            if (i == matchSel)
            {
                if (line.charAt(0) == '"')
                    pos = line.length();
                else
                {
                    pos = line.indexOf('(');
                    if (pos == -1)
                        pos = line.indexOf(':') - 1;
                }
                if (start >= 0 && end >= start)
                	line = "<B>" + line.substring(0, pos) + "</B>" + line.substring(pos, start) + "<B>" + line.substring(start, end) + "</B>" + line.substring(end);
                else
                	line = "<B>" + line.substring(0, pos) + "</B>" + line.substring(pos);
            }
            text += line;
            if (i < size - 1)
                text += "<BR>\n";
        }
        return text;
    }

    protected class SB_StackData
    {

        int first, count; // mid string location

        int index; // parameter index

        SB_StackData(int f)
        {
            first = f;
            count = 0;
            index = 0;
        }
    }

    /** in: expression and selection position
     *  out: function, parameter, parameter index, parenthesis level
     * @param expr
     * @param pos
     * @param info
     */
    protected void parseFunction(String expr, int pos, SB_ParseInfo info)
    {
        Stack pred_stack = new Stack();
        pred_stack.push(new SB_StackData(0));
        boolean in_quotes = false;
        SB_StackData stackData;
        int j = -1;
        for (int i = 0; i < pos; i++)
        {
            char c = expr.charAt(i);
            if (c == '"')
            {
                in_quotes = !in_quotes;
                if (in_quotes)
                    j = i;
            }
            if (in_quotes)
                continue;
            stackData = (SB_StackData) pred_stack.peek();
            switch (c)
            {
            case '(':
                pred_stack.push(new SB_StackData(i + 1));
                break;
            case ',':
                stackData.first = i + 1;
                stackData.count = 0;
                stackData.index++;
                break;
            case ')':
                pred_stack.pop();
                if (pred_stack.empty())
                {
                    info.funcName = "";
                    info.index = -1;
                    info.paren = 0;
                    return;
                }
                stackData = (SB_StackData) pred_stack.peek();
                stackData.first = i + 1;
                stackData.count = 0;
                break;
            default:
                if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')
                        || c == '_' || c == '.')
                {
                    stackData.count++;
                } else
                {
                    stackData.first = i + 1;
                    stackData.count = 0;
                }
            }
        }

        info.paren = pred_stack.size() - 1;
        if (info.paren > 0)
        {
            stackData = (SB_StackData) pred_stack.peek();
            if (in_quotes || (pos > 0 && expr.charAt(pos - 1) == '"'))
                info.paramName = expr.substring(j, pos);
            else
                info.paramName = expr.substring(stackData.first, stackData.first + stackData.count);
            info.index = stackData.index;
            pred_stack.pop();
        } else
        {
            info.index = -1;
        }
        stackData = (SB_StackData) pred_stack.peek();
        info.first = stackData.first;
        info.funcName = expr.substring(stackData.first, stackData.first + stackData.count);
    }

    /**
     * if found, select given function
     * @param matchList
     * @param text
     * @param index
     * @param firstParen
     */
    protected void matchFunction(Vector matchList, String text, int index, boolean firstParen)
    {
        Vector functions;
        if (firstParen)
            functions = _actionsBehaviors;
        else
            functions = _predicates;
        DefaultMutableTreeNode treeNode;
        SB_Function function;
        String funcName;
        int size = functions.size();
        for (int i = 0; i < size; i++)
        {
            treeNode = (DefaultMutableTreeNode) functions.get(i);
            function = ((SB_Function) treeNode.getUserObject());
            funcName = function.getName();
            if (text.equals(funcName))
            {
                matchList.add(getFullName(treeNode, index));
                return;
            }
        }
    }

    protected String getFullName(DefaultMutableTreeNode treeNode, int index)
    {
        SB_Function function = ((SB_Function) treeNode.getUserObject());
        String fullName = function.getName() + "(";
        DefaultMutableTreeNode childNode;
        SB_Parameter param;
        int size = treeNode.getChildCount();
        for (int i = 0; i < size; ++i)
        {
            childNode = (DefaultMutableTreeNode) treeNode.getChildAt(i);
            param = ((SB_Parameter) childNode.getUserObject());
            if (i == index)
                fullName += "<B>";
            fullName += param.getName() + " : " + param.getTypeName();
            if (i == index)
                fullName += "</B>";
            if (i < size - 1)
                fullName += ", ";
        }
        fullName += ")";
        if (function instanceof SB_Predicate)
            fullName += " : " + ((SB_Predicate) function).getRetTypeName();
        return fullName;
    }
    
	protected String buildMethodString(SB_ClassMethod method, int paramIndex){
		StringBuffer sb = new StringBuffer(method.getName());
		sb.append("(");
		int size = method.getParams().size();
		int start = -1, end = -1;
		for (int i = 0; i < size; i ++)
		{
			SB_ClassMethodParameter param = (SB_ClassMethodParameter) method.getParams().get(i);
			if (i == paramIndex)
				start = sb.length();
			sb.append(param.getName());
			sb.append(" : ");
			sb.append(param.getType());
			if (i < size - 1)
				sb.append(", ");
			if (i == paramIndex)
				end = sb.length();
		}
		sb.append(") : ");
		sb.append(method.getReturnType());
		// append the start and end of the parameter that needs to be highlighted,
		// not highlight it directly because only the currently selected one needs to be highlighted
		// and there could be multiple methods with the same name (polymophism),
		// generateMatchText method is changed accordingly to handle this information
		if (start >= 0 && end >= start)
		{
			sb.append("@");
			sb.append(start);
			sb.append(",");
			sb.append(end);
		}
		return sb.toString();
	}	

    /**
     * if found, select given partial function
     * @param matchList
     * @param text
     * @param firstParen
     */
    protected void matchPartialFunction(Vector matchList, String text, boolean firstParen)
    {
        if (text.length() == 0)
            return;

        boolean found = false;
        Vector functions;
        if (firstParen)
            functions = _actionsBehaviors;
        else
            functions = _predicates;
        DefaultMutableTreeNode treeNode;
        SB_Function function;
        String funcName;
        int size = functions.size();
        for (int i = 0; i < size; i++)
        {
            treeNode = (DefaultMutableTreeNode) functions.get(i);
            function = ((SB_Function) treeNode.getUserObject());
            funcName = function.getName();
            if (text.regionMatches(0, funcName, 0, text.length()))
            {
                matchList.add(getFullName(treeNode, -1));
                found = true;
            }
            // else if (found)
            // return;
        }
    }

    /**
     *  if found, select given partial variable
     * @param matchList
     * @param text
     */
    protected void matchPartialVariable(Vector matchList, String text)
    {
        if (text.length() == 0)
            return;

        boolean found = false;
        SB_Variable var;
        String varName;
        int size = _variables.size();
        for (int i = 0; i < size; i++)
        {
            var = (SB_Variable) _variables.get(i);
            varName = var.getName();
            if (text.regionMatches(0, varName, 0, text.length()))
            {
                matchList.add(var.toString());
                found = true;
            }
            // else if (found)
            // return;
        }
    }

    protected void matchPartialExtra(Vector matchList, String expr, int pos, SB_ParseInfo info)
    {
    }
    
    protected void matchClassMemberOrMethod(String text, boolean voidAllowed, int paramIndex)
    {
    	int idx = text.lastIndexOf('.');
    	String lhe = text.substring(0, idx);
    	SB_Class clsInfo = getLeftHandClass(lhe);
    	if (clsInfo == null)
    		return;
    	
    	boolean staticOnly = (getClassByName(lhe) != null);
    	String rhe = (idx == text.length()-1 ? "" : text.substring(idx+1));
    	
    	try
    	{
    		Class cls = Class.forName(clsInfo.getName());
    		
    		// try to match with members
    		int size = clsInfo.getMembers().size();
    		for (int i = 0; i < size; i ++)
    		{
    			SB_ClassMember member = (SB_ClassMember) clsInfo.getMembers().get(i);
    			try
    			{
    				// skip if requires static member and the member is not static
    				if (staticOnly && !Modifier.isStatic(cls.getField(member.getName()).getModifiers()))
    					continue;
    			}
    			catch (NoSuchFieldException ex)
    			{
    				continue;
    			}
    			// add the member to the match list if name matches
    			if (member.getName().startsWith(rhe))
    				_matchList.add(member.toString());
    		}
    		
    		// try to match with methods
    		size = clsInfo.getMethods().size();
    		for (int i = 0; i < size; i ++)
    		{
    			SB_ClassMethod method = (SB_ClassMethod) clsInfo.getMethods().get(i);
    			// skip if the number of parameters does not match
    			if (paramIndex >= method.getParams().size())
    				continue;
    			// skip if requires return value and this method doesn't
    			if (!voidAllowed && "void".equals(method.getReturnType()))
    				continue;
    			try
    			{
    				// skip if requires static and this method is not static
        			if (staticOnly && !Modifier.isStatic(cls.getMethod(method.getName(), method.getParamTypeClassArray()).getModifiers()))
        				continue;
    			}
    			catch (NoSuchMethodException ex)
    			{
    				continue;
    			}
    			// add the method to the match list if name matches
    			if (method.getName().startsWith(rhe))
    				_matchList.add(buildMethodString(method, paramIndex));
    		}
    	}
    	catch (ClassNotFoundException ex)
    	{
    		return;
    	}
    }
    
    private SB_Class getLeftHandClass(String text)
    {
    	int idx = text.lastIndexOf('.');
    	if (idx < 0)
    	{
    		SB_Class cls = getClassByName(text);
    		if (cls != null)
    			return cls;
    		
    		int size = _variables.size();
    		for (int i = 0; i < size; i ++)
    		{
                SB_Variable var = (SB_Variable) _variables.get(i);
    			if (text.equals(var.getName()))
    				return getClassByName(var.getTypeName());
    		}
    		
    		return null;
    	}
    	
		String lhe = text.substring(0, idx);
    	SB_Class leftClass = getLeftHandClass(lhe);
    	if (leftClass == null)
    		return null;
    	
    	String rhe = text.substring(idx+1);
    	
		// try to match with members
		int size = leftClass.getMembers().size();
		for (int i = 0; i < size; i ++)
		{
			SB_ClassMember member = (SB_ClassMember) leftClass.getMembers().get(i);
			if (member.getName().startsWith(rhe))
				return ComponentRegistry.getProjectBar().getCatalog().lookupClass(member.getType());
		}
		
		// try to match with methods
		size = leftClass.getMethods().size();
		for (int i = 0; i < size; i ++)
		{
			SB_ClassMethod method = (SB_ClassMethod) leftClass.getMethods().get(i);
			if (method.getName().startsWith(rhe))
				return ComponentRegistry.getProjectBar().getCatalog().lookupClass(method.getReturnType());
		}
		
    	return null;
    }
    
    private SB_Class getClassByName(String name)
    {
    	// try to look up class directly, only work if name is the fully qualified name
    	SB_Class cls = ComponentRegistry.getProjectBar().getCatalog().lookupClass(name);
    	if (cls != null)
    		return cls;
    	
    	// name is not fully qualified, alias instead
    	return ComponentRegistry.getProjectBar().getCatalog().lookupClassByAlias(name);
    }
}

class SB_GlassPane extends JPanel
{
    protected JLabel _label;

    public SB_GlassPane()
    {
        setLayout(null);
        setOpaque(false);

        _label = new JLabel("<HTML><BODY bgcolor=yellow>Autocomplete</BODY></HTML>");
        _label.setSize(new Dimension(750, 750));
        _label.setFont(new Font("Arial", Font.PLAIN, 12));
        _label.setVerticalAlignment(SwingConstants.TOP);
        add(_label);
    }

    protected void setLocation(JTextField textField)
    {
        Container container = textField.getParent();
        while (container != null && !(container instanceof Window))
            container = container.getParent();

        // int offset = 0;
        int offset = -3;
        if (container instanceof JFrame)
        {
            ((JFrame) container).setGlassPane(this);
            // offset = 4;
        } else if (container instanceof JDialog)
            ((JDialog) container).setGlassPane(this);
        Point textFieldLocation = textField.getLocationOnScreen();
        Point windowLocation = container.getLocationOnScreen();
        _label.setLocation(textFieldLocation.x - windowLocation.x, textFieldLocation.y
                - windowLocation.y + offset);
    }

    protected void setText(String text)
    {
        if (text == null || text.length() == 0)
            _label.setText(null);
        else
            _label.setText("<HTML><BODY bgcolor=yellow>" + text + "</BODY></HTML>");
    }
}
